上次我們介紹了 MySQL 的簡單 CRUD,雖然本系列本來只打算介紹基礎中的基礎,然後再開傳送們給有興趣的邦友延伸閱讀,但是後來想一想,介紹 MySQL 沒講 JOIN 跟 Stored Procedure (SP)總覺得怪怪的,所以筆者最後決定資料庫多寫兩天,簡單的介紹一下 JOIN 跟 SP。今天我們就來介紹一下資料庫超級常用的 JOIN。
一般而言,我們會盡量避免讓同樣的資料散落在不同的資料表。我們會把這些共同會用到的資料「抽出來」放在另一張表,然後透過一個共通的欄位做關聯,從兩個資料表把資料「串」起來,而這個「串」的動作就是 JOIN。
MySQL 的 JOIN 有三種:INNER JOIN, LEFT OUTER JOIN與 RIGHT OUTER JOIN。假設我們用一個共通欄位 c 來做關聯
INNER 與 OUTER 兩個字通常會被省略,變成 JOIN, LEFT JOIN, RIGHT JOIN
另外有一種 JOIN 方式叫做 FULL JOIN,只要兩個資料表任何一邊有資料就讀出,但是使用的機會少,MySQL 也沒有內建支援這個語法,所以本篇就不介紹。
現在我們新增了一張表:user_log,用來儲存每個使用者的行為紀錄。user_log 與 user 兩個資料表的共通欄位是 user_id,以下我們將用這個欄位來做 JOIN 示範。

user_log 中的 action 其實也應該抽出來放在另一張表,但是為了方便解釋語法,這邊先讓這些資料直接寫在這。
SELECT U.user_id, U.user_name, L.action, L.action_datetime
FROM user AS U INNER JOIN user_log AS L
ON U.user_id = L.user_id
上面的 SQL 腳本以共通的欄位 user_id 對 user 與 user_log 兩張資料表做關聯,從 user 資料表取出 user_id 與 user_name 兩個欄位、從 user_log 取出 action 與 action_datetime 欄位。AS 關鍵字可以替來源資料表取別名,上面的腳本中,U 是 user 資料表的別名,L 是 user_log 資料表的別名。ON 關鍵字用來做關聯的判斷,在上面的例子中,關聯成立的條件是 user 與 user_log 兩張表中的 user_id 必須相等。
從 JOIN 的結果我們可以看到,我們把使用者資料與 log 關聯起來了。但是INNERR JOIN 不會取得 user_id = 3 的資料與 log_id = 5 的資料,因為這些資料的 user_id 無法在另一張表中被匹配到。
SELECT U.user_id, U.user_name, L.action, L.action_datetime
FROM user AS U LEFT OUTER JOIN user_log AS L
ON U.user_id = L.user_id
上面的語法一樣以 user_id 欄位做關聯取出使用者名稱與 log 紀錄,唯一的差別是,沒有 log 紀錄的 Cathy 也出現在結果中了,因為 Cathy 是左邊的來源資料表,即使他沒有匹配到任何一筆 log 他仍然被讀取出來。
SELECT U.user_id, U.user_name, L.action, L.action_datetime
FROM user AS U RIGHT OUTER JOIN user_log AS L
ON U.user_id = L.user_id
上面的語法一樣以 user_id 欄位做關聯取出使用者名稱與 log 紀錄,唯一的差別是,bug 觸發的 log 也出現在查詢結果中了,即使他的 user_id 是 -1 沒有匹配到任何使用者。而且我們的指令中選擇從 user 資料表取出 user_id,所以這邊這筆 log 的 user_id 變成了 NULL
上面的語法雖然都可以運作,但卻不是實務上最常寫的方式,實務上的慣例為
INNER 通常會省略,只寫 SELECT ... FROM t1 JOIN t2 ON ...。甚至可以連 JOIN 都省略,只用逗號隔開兩個資料表,然後用 WHERE 取代 ON,寫成 SELECT ... FROM t1, t2 WHERE ...
OUTER 通常會省略,寫成 LEFT JOIN 或 RIGHT JOIN
大多會用 LEFT JOIN。只要把資料表的順序對調,RIGHT JOIN 就會變成 LEFT JOIN,實務上比較少會用 RIGHT JOIN,大多會把順序倒過來用 LEFT JOIN
明天,我們將繼續來介紹 Stored Procedure,介紹完就來幫我們的 .NET API 連上資料庫。